package com.bite.messageQueue.mqClient;

import com.bite.messageQueue.common.*;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 消息队列（MQ）客户端与服务器建立和管理网络连接的类
 */
public class Connection {
    private Socket socket = null;
    //管理多个channel，用哈希表组织
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();

    private InputStream inputStream;

    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    private ExecutorService callbackPool = null;

    /**
     * 初始化 TCP 连接，创建输入/输出流（DataInputStream/DataOutputStream）。
     * 启动独立线程持续监听 Socket 响应，通过 readResponse() 读取服务器返回的数据。
     * 使用固定大小线程池（Executors.newFixedThreadPool(4)）处理回调任务，
     * 避免阻塞主线程，类似 RabbitMQ 客户端的异步回调机制。
     * @param host broker server 的ip地址
     * @param port broker server端口号
     */
    public Connection(String host, int port) throws IOException {
        this.socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        callbackPool = Executors.newFixedThreadPool(4);
        //创建一个扫描线程, 由这个线程负责不停的从 socket 中读取响应数据.
        //把这个响应数据再交给对应的 channel 负责处理.
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                // 连接正常断开的. 此时这个异常直接忽略.
                System.out.println("[Connection] 连接正常断开!");
            } catch (IOException | ClassNotFoundException | MqException e) {
                System.out.println("[Connection] 连接异常断开!");
                e.printStackTrace();
            }
        });
        t.start();
    }

    /**
     * 控制请求响应：将结果存入对应 Channel 的 basicReturnsMap，
     * 唤醒阻塞的 waitResult() 线程。
     * 服务器推送（类型 0xC）：通过线程池触发 Channel 的消费者回调（consumer.handleDelivery），
     * 类似 RabbitMQ 的 AsyncEventingBasicConsumer 设计。
     * @param response 服务器响应
     */
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc) {
            // 服务器推送来的消息数据
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据 channelId 找到对应的 channel 对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if (channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在! channelId=" + channel.getChannelId());
            }
            // 执行该 channel 对象内部的回调.
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(), subScribeReturns.getBasicProperties(),
                            subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            // 当前响应是针对刚才的控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            // 把这个结果放到对应的 channel 的 hash 表中.
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在! channelId=" + channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

    /**
     * 将请求序列化为二进制流：先写入类型（type），
     * 再写入长度（length），最后写入有效载荷（payload）。
     * 使用 DataOutputStream 实现网络传输，
     * 类似 RocketMQ 的 sendKernelImpl 方法中的序列化逻辑。
     * @param request 要发送的请求
     */
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求! type=" + request.getType() + ", length=" + request.getLength());
    }

    /**
     * 按解析响应：先读取类型和长度，再根据长度读取完整载荷。
     * 校验数据完整性（n != response.getLength()），
     * 防止数据截断，类似 EMQX 客户端的消息完整性检查机制
     */
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if (n != response.getLength()) {
            throw new IOException("读取的响应数据不完整!");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应! type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }

    /**
     * 生成唯一 channelId（基于 UUID），创建 Channel 对象并注册到 channelMap。
     * 调用 channel.createChannel() 向服务器发送创建请求，失败时自动清理映射表，
     * 类似 RabbitMQ 的Channel` 逻辑
     * @return channel
     */
    public Channel createChannel() throws IOException, InterruptedException {
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId, this);
        //添加进管理channel的hash表中
        channelMap.put(channelId, channel);
        boolean ok = channel.createChannel();
        if (!ok) {
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }

    /**
     * 释放所有资源：关闭线程池、清空通道映射、关闭 Socket 和流。
     */
    public void close() throws IOException {
        try{
            callbackPool.shutdown();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
            System.out.println("释放socket");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取
     * @return socket
     */
    public Socket getSocket() {
        return socket;
    }

    /**
     * 设置
     * @param socket
     */
    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    /**
     * 获取
     * @return channelMap
     */
    public ConcurrentHashMap<String, Channel> getChannelMap() {
        return channelMap;
    }

    /**
     * 设置
     * @param channelMap
     */
    public void setChannelMap(ConcurrentHashMap<String, Channel> channelMap) {
        this.channelMap = channelMap;
    }

    /**
     * 获取
     * @return inputStream
     */
    public InputStream getInputStream() {
        return inputStream;
    }

    /**
     * 设置
     * @param inputStream
     */
    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    /**
     * 获取
     * @return outputStream
     */
    public OutputStream getOutputStream() {
        return outputStream;
    }

    /**
     * 设置
     * @param outputStream
     */
    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    /**
     * 获取
     * @return dataInputStream
     */
    public DataInputStream getDataInputStream() {
        return dataInputStream;
    }

    /**
     * 设置
     * @param dataInputStream
     */
    public void setDataInputStream(DataInputStream dataInputStream) {
        this.dataInputStream = dataInputStream;
    }

    /**
     * 获取
     * @return dataOutputStream
     */
    public DataOutputStream getDataOutputStream() {
        return dataOutputStream;
    }

    /**
     * 设置
     * @param dataOutputStream
     */
    public void setDataOutputStream(DataOutputStream dataOutputStream) {
        this.dataOutputStream = dataOutputStream;
    }

    @Override
    public String toString() {
        return "Connection{socket = " + socket + ", channelMap = " + channelMap + ", inputStream = " + inputStream + ", outputStream = " + outputStream + ", dataInputStream = " + dataInputStream + ", dataOutputStream = " + dataOutputStream + "}";
    }
}
